using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows;
using System.Windows.Interop;
using Standard;
using HANDLE_MESSAGE = System.Collections.Generic.KeyValuePair<Standard.WM, Standard.MessageHandler>;

namespace ChromeWindow;

public class CustomChromeWindow : Window
{
    private IntPtr WindowHandle;
    private readonly List<HANDLE_MESSAGE> MessageTable;
    private WindowState LastMenuState;
    
    protected CustomChromeWindow()
    {
        Dpi = this.GetDpi();

        MessageTable = new List<HANDLE_MESSAGE>
        {
            new(WM.ACTIVATE, HandleActivate),
            new(WM.NCCALCSIZE, HandleNCCaleSize),
            new(WM.NCHITTEST, HandleNCHitTest)
        };
    }


    private static readonly Type TypeOfThis = typeof(CustomChromeWindow);
    private static CustomChromeWindow GetView(DependencyObject d) => (CustomChromeWindow)d;


    #region OuterResizeBorderThicknessProperty

    /// <summary>
    /// 窗口外边调整尺寸的边框大小（单位：Point）
    /// </summary>
    private Thickness OuterResizeBorderThickness
    {
        get
        {
            var resizeBorder = SystemParameters.WindowResizeBorderThickness;
            var paddedBorder = PaddedBorderThickness;

            return new Thickness
            (
                resizeBorder.Left + paddedBorder.Left,
                resizeBorder.Top + paddedBorder.Top,
                resizeBorder.Right + paddedBorder.Right,
                resizeBorder.Bottom + paddedBorder.Bottom
            );
        }
    }

    /// <summary>
    /// 窗口外边调整尺寸的边框大小（单位：Pixel）
    /// </summary>
    private Thickness OuterResizeBorderThicknessPixel
        => DpiHelper.LogicalThicknessToDevice(OuterResizeBorderThickness, Dpi.DpiScaleX, Dpi.DpiScaleY);

    #endregion


    #region InnerResizeBorderThicknessProperty

    /// <summary>
    /// 窗口内调整尺寸的边框大小（单位：Point）
    /// </summary>
    public Thickness InnerResizeBorderThickness
    {
        get => (Thickness)GetValue(InnerResizeBorderThicknessProperty);
        set => SetValue(InnerResizeBorderThicknessProperty, value);
    }

    public static readonly DependencyProperty InnerResizeBorderThicknessProperty =
        DependencyProperty.Register(
            nameof(InnerResizeBorderThickness),
            typeof(Thickness),
            TypeOfThis,
            new PropertyMetadata(new Thickness(4d)));

    /// <summary>
    /// 窗口内调整尺寸的边框大小（单位：Pixel）
    /// </summary>
    private Thickness InnerResizeBorderThicknessPixel
        => DpiHelper.LogicalThicknessToDevice(InnerResizeBorderThickness, Dpi.DpiScaleX, Dpi.DpiScaleY);

    #endregion


    #region ResizeBorderThickness

    /// <summary>
    /// 调整尺寸边框的大小（单位：Point）
    /// </summary>
    public Thickness ResizeBorderThickness
        => DpiHelper.DeviceThicknessToLogical(ResizeBorderThicknessPixel, Dpi.DpiScaleX, Dpi.DpiScaleY);

    /// <summary>
    /// 调整尺寸边框的大小（单位：像素）
    /// </summary>
    private Thickness ResizeBorderThicknessPixel
    {
        get
        {
            var outer = OuterResizeBorderThicknessPixel;
            var inner = InnerResizeBorderThicknessPixel;
            return new Thickness(
                outer.Left + inner.Left,
                outer.Top + inner.Top,
                outer.Right + inner.Right,
                outer.Bottom + inner.Bottom);
        }
    }

    #endregion


    #region CaptionHeightProperty

    // todo: 当标题栏高度变化后要更新

    /// <summary>
    /// 标题栏高度（单位：Point）
    /// </summary>
    public double CaptionHeight
    {
        get => (double)GetValue(CaptionHeightProperty);
        set => SetValue(CaptionHeightProperty, value);
    }

    public static readonly DependencyProperty CaptionHeightProperty =
        DependencyProperty.Register(
            nameof(CaptionHeight),
            typeof(double),
            TypeOfThis,
            new PropertyMetadata(SystemParameters.CaptionHeight));

    /// <summary>
    /// 标题栏高度（单位：Pixel）
    /// </summary>
    private double CaptionHeightPixel => CaptionHeight * Dpi.DpiScaleY;

    #endregion


    #region CaptionLeftRightResizeModeProperty

    /// <summary>
    /// 标题栏左右两边调整尺寸的模式，可以设置为仅能水平调整，或者为左上/右上角调整尺寸
    /// </summary>
    public CaptionLeftRightResizeMode CaptionLeftRightResizeMode
    {
        get => (CaptionLeftRightResizeMode)GetValue(CaptionLeftRightResizeModeProperty);
        set => SetValue(CaptionLeftRightResizeModeProperty, value);
    }

    public static readonly DependencyProperty CaptionLeftRightResizeModeProperty =
        DependencyProperty.Register(
            nameof(CaptionLeftRightResizeMode),
            typeof(CaptionLeftRightResizeMode),
            TypeOfThis,
            new PropertyMetadata(CaptionLeftRightResizeMode.Horizontal));

    #endregion


    #region NonclientHitTestResult

    /// <summary>
    /// 是否在非客户区域可被点击
    /// </summary>
    public static readonly DependencyProperty NonClientHitTestResultProperty =
        DependencyProperty.RegisterAttached("NonClientHitTestResult", typeof(NonClientHitTestResult?), TypeOfThis);

    public static NonClientHitTestResult? GetNonClientHitTestResult(DependencyObject obj)
    {
        return (NonClientHitTestResult?)obj.GetValue(NonClientHitTestResultProperty);
    }

    public static void SetNonClientHitTestResult(DependencyObject obj, bool value)
    {
        obj.SetValue(NonClientHitTestResultProperty, value);
    }

    #endregion


    #region PaddedBorderThickness

    /// <summary>
    /// 返回带标题窗口的边框填充量（以Point为单位）。Windows XP/2000：不支持此值。
    /// </summary>
    public Thickness PaddedBorderThickness
        => DpiHelper.DeviceThicknessToLogical(PaddedBorderThicknessPixel, Dpi.DpiScaleX, Dpi.DpiScaleY);

    /// <summary>
    /// 返回带标题窗口的边框填充量（以像素为单位）。Windows XP/2000：不支持此值。
    /// 参考：https://www.cnblogs.com/dino623/p/problems_of_WindowChrome.html
    ///      https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-getsystemmetrics
    /// Firefox源码： https://github.com/mozilla/gecko-dev/blob/master/widget/windows/nsWindow.cpp
    /// </summary>
    private Thickness PaddedBorderThicknessPixel
    {
        [SecurityCritical]
        get
        {
            const int SM_CXPADDEDBORDER = 92;

            if (PaddedBorderThicknessCache != null) return PaddedBorderThicknessCache.Value;

            var paddedBorder = NativeMethods.GetSystemMetrics(SM_CXPADDEDBORDER);
            var frameSize = new Size(paddedBorder, paddedBorder);
            var frameSizeInDips = DpiHelper.DeviceSizeToLogical(frameSize, Dpi.DpiScaleX, Dpi.DpiScaleY);
            PaddedBorderThicknessCache = new Thickness(frameSizeInDips.Width, frameSizeInDips.Height, frameSizeInDips.Width, frameSizeInDips.Height);
            return PaddedBorderThicknessCache.Value;
        }
    }

    private Thickness? PaddedBorderThicknessCache;

    #endregion


    #region 消息处理

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);

        WindowHandle = new WindowInteropHelper(this).Handle;
        HwndSource.FromHwnd(WindowHandle)?.AddHook(Hook);
    }

    private IntPtr Hook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        Assert.AreEqual(hwnd, WindowHandle);

        var message = (WM)msg;
        foreach (var handlePair in MessageTable.Where(pair => pair.Key == message))
        {
            return handlePair.Value(message, wParam, lParam, out handled);
        }

        return IntPtr.Zero;
    }

    private IntPtr HandleActivate(WM umsg, IntPtr wparam, IntPtr lparam, out bool handled)
    {
        // 在窗口激活时扩展窗口的边框，参考：https://learn.microsoft.com/zh-cn/windows/win32/dwm/customframe

        var extend = OuterResizeBorderThickness;
        // extend.Top += CaptionHeight;
        extend.Top = 0;

        var nonClientArea = DpiHelper.LogicalThicknessToDevice(extend, Dpi.DpiScaleX, Dpi.DpiScaleY);
        var dwmMargin = new MARGINS
        {
            // err on the side of pushing in glass an extra pixel.
            cxLeftWidth = (int)Math.Ceiling(nonClientArea.Left),
            cxRightWidth = (int)Math.Ceiling(nonClientArea.Right),
            cyTopHeight = (int)Math.Ceiling(nonClientArea.Top),
            cyBottomHeight = (int)Math.Ceiling(nonClientArea.Bottom),
        };

        NativeMethods.DwmExtendFrameIntoClientArea(WindowHandle, ref dwmMargin);

        handled = true;
        return IntPtr.Zero;
    }

    private IntPtr HandleNCCaleSize(WM umsg, IntPtr wParam, IntPtr lParam, out bool handled)
    {
        // 在计算窗口工作区的大小和位置时触发，参考：https://learn.microsoft.com/zh-cn/windows/win32/winmsg/wm-nccalcsize

        handled = true;

        // var extend = ResizeBorderThickness;
        // var extend = OuterResizeBorderThickness; // 这里只能是外围边框范围
        // // extend.Top += CaptionHeight;
        //
        // var extendPixel = DpiHelper.LogicalThicknessToDevice(extend, Dpi.DpiScaleX, Dpi.DpiScaleY);
        //
        // var rcClientArea = (RECT)Marshal.PtrToStructure(lParam, typeof(RECT))!;
        // rcClientArea.Top += 0;
        // rcClientArea.Left += (int)extendPixel.Left;
        // rcClientArea.Bottom -= (int)extendPixel.Bottom;
        // rcClientArea.Right -= (int)extendPixel.Right;
        //
        // Marshal.StructureToPtr(rcClientArea, lParam, false);

        // Per MSDN for NCCALCSIZE, always return 0 when wParam == FALSE
        // 
        // Returning 0 when wParam == TRUE is not appropriate - it will preserve
        // the old client area and align it with the upper-left corner of the new 
        // client area. So we simply ask for a redraw (WVR_REDRAW)

        var retVal = IntPtr.Zero;
        if (wParam.ToInt32() != 0) // wParam == TRUE
        {
            retVal = new IntPtr((int)WVR.REDRAW);
        }

        return retVal;
    }

    private IntPtr HandleNCHitTest(WM umsg, IntPtr wparam, IntPtr lparam, out bool handled)
    {
        // 计算鼠标位于窗口哪个部分，具体参考：https://learn.microsoft.com/zh-cn/windows/win32/inputdev/wm-nchittest

        var mousePosScreen = new Point(Utility.GET_X_LPARAM(lparam), Utility.GET_Y_LPARAM(lparam));
        var windowRect = _GetWindowRect();

        var mousePosWindow = mousePosScreen;
        mousePosWindow.Offset(-windowRect.X, -windowRect.Y);
        mousePosWindow = DpiHelper.DevicePixelsToLogical(mousePosWindow, Dpi.DpiScaleX, Dpi.DpiScaleY);

        // 先把点击事件传到客户区，看看客户区有没有处理
        if (InputHitTest(mousePosWindow) is DependencyObject dp)
        {
            var result = GetNonClientHitTestResult(dp);
            if (result != null)
            {
                handled = true;
                return new IntPtr((int)result.Value);
            }
        }

        // 处理非客户区的命中测试
        var ht = _HitTestNca(
            DpiHelper.DeviceRectToLogical(windowRect, Dpi.DpiScaleX, Dpi.DpiScaleY),
            DpiHelper.DevicePixelsToLogical(mousePosScreen, Dpi.DpiScaleX, Dpi.DpiScaleY));

        handled = true;
        return new IntPtr((int)ht);
    }
    
    #endregion


    /// <summary>
    /// Get the bounding rectangle for the window in physical coordinates.
    /// </summary>
    /// <returns>The bounding rectangle for the window.</returns>
    /// <SecurityNote>
    ///   Critical : Calls critical methods
    /// </SecurityNote>
    [SecurityCritical]
    private Rect _GetWindowRect()
    {
        // Get the window rectangle.
        var windowPosition = NativeMethods.GetWindowRect(WindowHandle);
        return new Rect(windowPosition.Left, windowPosition.Top, windowPosition.Width, windowPosition.Height);
    }


    /// <summary>
    /// Matrix of the HT values to return when responding to NC window messages.
    /// </summary>
    [SuppressMessage("Microsoft.Performance", "CA1814:PreferJaggedArraysOverMultidimensional", MessageId = "Member")]
    private static readonly HT[,] HitTestBorders =
    {
        { HT.TOPLEFT, HT.TOP, HT.TOPRIGHT },
        { HT.LEFT, HT.CLIENT, HT.RIGHT },
        { HT.BOTTOMLEFT, HT.BOTTOM, HT.BOTTOMRIGHT },
    };

    private HT _HitTestNca(Rect windowPosition, Point mousePosition)
    {
        // 默认设置所在位置为中间（1,1）
        var uRow = 1;
        var uCol = 1;
        var onResizeBorder = false;

        Thickness border = ResizeBorderThickness;
        border.Top = SystemParameters.WindowResizeBorderThickness.Top;

        // 判断垂直方向在哪
        if (mousePosition.Y >= windowPosition.Top && mousePosition.Y < windowPosition.Top + CaptionHeight)
        {
            onResizeBorder = mousePosition.Y < windowPosition.Top + border.Top; // 落在顶部ResizeBorder上
            uRow = 0; // 顶边（点击到标题栏或者ResizeBorder）
        }
        else if (mousePosition.Y < windowPosition.Bottom && mousePosition.Y >= windowPosition.Bottom - border.Bottom)
        {
            uRow = 2; // 底边
        }

        // 判断水平方向在哪
        if (mousePosition.X >= windowPosition.Left && mousePosition.X < windowPosition.Left + border.Left)
        {
            uCol = 0; // 左边
        }
        else if (mousePosition.X < windowPosition.Right && mousePosition.X >= windowPosition.Right - border.Right)
        {
            uCol = 2; // 右边
        }

        // 处理鼠标位于顶部标题栏两侧的情况
        if (uRow == 0 && uCol != 1 && !onResizeBorder)
        {
            uRow = CaptionLeftRightResizeMode == CaptionLeftRightResizeMode.Horizontal ? 1 : 0;
        }

        var ht = HitTestBorders[uRow, uCol];

        if (ht == HT.TOP && !onResizeBorder)
        {
            ht = HT.CAPTION;
        }

        return ht;
    }

    
    /// <summary>
    /// Get the WindowState as the native HWND knows it to be.  This isn't necessarily the same as what Window thinks.
    /// </summary>
    /// <SecurityNote>
    ///   Critical : Calls critical methods
    /// <SecurityNote>
    [SecurityCritical]
    private WindowState _GetHwndState()
    {
        var wpl = NativeMethods.GetWindowPlacement(WindowHandle);
        switch (wpl.showCmd)
        {
            case SW.SHOWMINIMIZED: return WindowState.Minimized;
            case SW.SHOWMAXIMIZED: return WindowState.Maximized;
        }
        return WindowState.Normal;
    }
    

    /// <summary>
    /// Update the items in the system menu based on the current, or assumed, WindowState.
    /// </summary>
    /// <param name="assumeState">
    /// The state to assume that the Window is in.  This can be null to query the Window's state.
    /// </param>
    /// <remarks>
    /// We want to update the menu while we have some control over whether the caption will be repainted.
    /// </remarks>
    /// <SecurityNote>
    ///   Critical : Calls critical methods
    /// <SecurityNote>
    [SecurityCritical]
    private void _UpdateSystemMenu(WindowState? assumeState)
    {
        const MF mfEnabled = MF.ENABLED | MF.BYCOMMAND;
        const MF mfDisabled = MF.GRAYED | MF.DISABLED | MF.BYCOMMAND;

        WindowState state = assumeState ?? _GetHwndState();

        if (null != assumeState || LastMenuState != state)
        {
            LastMenuState = state;

            bool modified = _ModifyStyle(WS.VISIBLE, 0);
            IntPtr hmenu = NativeMethods.GetSystemMenu(WindowHandle, false);
            if (IntPtr.Zero != hmenu)
            {
                var dwStyle = (WS)NativeMethods.GetWindowLongPtr(WindowHandle, GWL.STYLE).ToInt32();

                bool canMinimize = Utility.IsFlagSet((int)dwStyle, (int)WS.MINIMIZEBOX);
                bool canMaximize = Utility.IsFlagSet((int)dwStyle, (int)WS.MAXIMIZEBOX);
                bool canSize = Utility.IsFlagSet((int)dwStyle, (int)WS.THICKFRAME);

                switch (state)
                {
                    case WindowState.Maximized:
                        NativeMethods.EnableMenuItem(hmenu, SC.RESTORE, mfEnabled);
                        NativeMethods.EnableMenuItem(hmenu, SC.MOVE, mfDisabled);
                        NativeMethods.EnableMenuItem(hmenu, SC.SIZE, mfDisabled);
                        NativeMethods.EnableMenuItem(hmenu, SC.MINIMIZE, canMinimize ? mfEnabled : mfDisabled);
                        NativeMethods.EnableMenuItem(hmenu, SC.MAXIMIZE, mfDisabled);
                        break;
                    case WindowState.Minimized:
                        NativeMethods.EnableMenuItem(hmenu, SC.RESTORE, mfEnabled);
                        NativeMethods.EnableMenuItem(hmenu, SC.MOVE, mfDisabled);
                        NativeMethods.EnableMenuItem(hmenu, SC.SIZE, mfDisabled);
                        NativeMethods.EnableMenuItem(hmenu, SC.MINIMIZE, mfDisabled);
                        NativeMethods.EnableMenuItem(hmenu, SC.MAXIMIZE, canMaximize ? mfEnabled : mfDisabled);
                        break;
                    default:
                        NativeMethods.EnableMenuItem(hmenu, SC.RESTORE, mfDisabled);
                        NativeMethods.EnableMenuItem(hmenu, SC.MOVE, mfEnabled);
                        NativeMethods.EnableMenuItem(hmenu, SC.SIZE, canSize ? mfEnabled : mfDisabled);
                        NativeMethods.EnableMenuItem(hmenu, SC.MINIMIZE, canMinimize ? mfEnabled : mfDisabled);
                        NativeMethods.EnableMenuItem(hmenu, SC.MAXIMIZE, canMaximize ? mfEnabled : mfDisabled);
                        break;
                }
            }

            if (modified)
            {
                _ModifyStyle(0, WS.VISIBLE);
            }
        }
    }

    
    /// <summary>Add and remove a native WindowStyle from the HWND.</summary>
    /// <param name="removeStyle">The styles to be removed.  These can be bitwise combined.</param>
    /// <param name="addStyle">The styles to be added.  These can be bitwise combined.</param>
    /// <returns>Whether the styles of the HWND were modified as a result of this call.</returns>
    /// <SecurityNote>
    ///   Critical : Calls critical methods
    /// <SecurityNote>
    [SecurityCritical]
    private bool _ModifyStyle(WS removeStyle, WS addStyle)
    {
        Assert.IsNotDefault(WindowHandle);
        var dwStyle = (WS)NativeMethods.GetWindowLongPtr(WindowHandle, GWL.STYLE).ToInt32();
        var dwNewStyle = (dwStyle & ~removeStyle) | addStyle;
        if (dwStyle == dwNewStyle)
        {
            return false;
        }

        NativeMethods.SetWindowLongPtr(WindowHandle, GWL.STYLE, new IntPtr((int)dwNewStyle));
        return true;
    }

    #region Dpi

    private DpiScale Dpi;

    protected override void OnDpiChanged(DpiScale oldDpi, DpiScale newDpi)
    {
        Dpi = newDpi;
        PaddedBorderThicknessCache = null;
        base.OnDpiChanged(oldDpi, newDpi);
    }

    #endregion
    
    
    /// <SecurityNote>
    ///   Critical : Calls critical methods
    /// <SecurityNote>
    [SecurityCritical]
    private void _ClearRoundingRegion()
    {
        NativeMethods.SetWindowRgn(WindowHandle, IntPtr.Zero, true);
    }

    private const SWP _SwpFlags = SWP.FRAMECHANGED | SWP.NOSIZE | SWP.NOMOVE | SWP.NOZORDER | SWP.NOOWNERZORDER | SWP.NOACTIVATE;

    
    protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
    {
        if (WindowHandle != IntPtr.Zero)
        {
            // _ClearRoundingRegion();
            // NativeMethods.SetWindowPos(WindowHandle, IntPtr.Zero, 0, 0, 0, 0, _SwpFlags);
        }
       
        base.OnRenderSizeChanged(sizeInfo);
    }
}